Racket 编程:一门用于设计和实现编程语言的语言
摘要
Racket 是一种通用、多范式的编程语言,但其最核心的定义在于,它是一个为编程语言设计和实现而打造的平台(language-oriented programming, LOP)。Racket 的历史可以追溯到其作为 Scheme 方言的起源,但它在 2010 年从 PLT Scheme 正式更名为 Racket,这一举动标志着其设计理念从 Scheme 的极简主义转向了一种更具实用主义、功能丰富的平台化方法。
Racket 的独特之处在于其强大的宏系统,它建立在被称为“语法对象”(syntax objects)的抽象之上。这种机制使得宏具有“卫生性”(hygienic),能够自动避免名称冲突,从而将宏从一个危险的工具转变为一个可靠、可组合的构建块。凭借这一核心能力,Racket 允许开发者创建与内置语言结构无异的嵌入式或领域特定语言(DSL)。
Racket 的应用领域广泛,横跨学术研究、计算机科学教育和小众商业项目。其旗舰集成开发环境 DrRacket 专为教学而设计,通过简化错误报告和提供交互式功能来帮助初学者。然而,与其他主流语言(如 Python)相比,Racket 的社区规模和生态系统较小,这在一定程度上限制了其在通用软件开发中的普及。与 Common Lisp 相比,Racket 在交互式开发方面做出了权衡,但其模块化和宏的卫生性提供了更高的健壮性和可预测性。
总而言之,Racket 并非仅仅是一种编程语言,而是一种用于创造语言的工具集,其核心设计旨在通过提供强大的元编程能力,赋予开发者前所未有的抽象和控制力。
第1章:Racket 的基础理念
Racket 的本质不能仅从其语法或数据结构来理解,而必须从其作为一门语言设计平台的独特哲学出发。这一理念源自其 Scheme 的血统,又通过其自身的演变而得以升华。
1.1 Racket 的传承:从 PLT Scheme 到一个平台
Racket 的历史始于 1995 年,最初名为 PLT Scheme,是一个旨在为教学提供编程环境的项目。多年来,该团队在 DrScheme(其集成开发环境)中加入了更多功能,使其超越了简单的 Scheme 实现,成为一个功能更丰富的系统。到 2010 年,该语言正式更名为 Racket,并发布了 5.0 版本。
这一名称变更是一个深思熟虑的战略性决定。正如其开发者所解释的,名称中的“Scheme”部分具有误导性,因为它使该语言被视为一种极简主义的 Scheme 实现,而这与 PLT 团队致力于创建一个强大、实用的语言平台的愿景相悖。通过更名为 Racket,开发者希望解决这个“沟通问题”,明确地将该语言定义为 Scheme 的一个特定后代,一个能够驱动 PLT 语言和库的独立实体。这一转变标志着其从“仅仅是 Scheme”的实现,发展为一门拥有自身身份、同时又能支持多种语言(包括 Scheme)的平台。
1.2 Lisp/Scheme 血统及其影响
作为 Lisp 家族的现代方言和 Scheme 的后代,Racket 继承了许多核心设计思想。它保留了 Lisp 最显著的特征之一:表达式导向的语法,所有代码都由括号包围的列表(S-表达式)构成。这种统一的语法使得代码本身可以被视为数据,程序和数据之间的等价性(program/data equivalence)使 Racket 成为构建编译器、调试器和结构化编辑器等程序的理想选择。
Racket 还从 Scheme 继承了对函数式编程和递归的推崇。其核心语言特性包括词法闭包、尾调用优化和高阶函数,这些都是函数式编程范式的基石。这种简洁而优雅的特性组合,使其成为一门极具影响力的编程语言。
1.3 现代方言的演变:实用主义与极简主义
Racket 与其 Scheme 祖先的关键区别在于其哲学上的转变:从 Scheme 的极简主义转向实用主义。Scheme 旨在成为一门核心构造极少的语言,而 Racket 则通过添加更丰富的库、工具和对实际应用的支持来扩展其功能。这种实用主义体现在以下几个方面:
首先,Racket 采用了多范式方法,支持函数式、命令式、元编程、面向语言、面向对象、模块化和逻辑式等多种编程范式。这与 Scheme 专注于函数式编程的理念形成鲜明对比。其次,Racket 的标准库更为庞大,并支持可变状态(mutation)。最后,其内置的 IDE DrRacket,提供了许多旨在提高用户友好性和生产力的功能,而这在极简主义的 Scheme 世界中并不常见。
这种从极简主义到实用主义的演变,是理解 Racket 核心身份的关键。它不再满足于只成为一门用于教学核心概念的纯粹语言,而是力求成为一个功能完备的平台,以应对通用编程的挑战。
第2章:语言创建的核心机制
Racket 作为语言平台的身份,其核心是由一组精心设计的技术机制所支撑。这些机制赋予了它独特的能力,使其能够超越传统的库和框架,实现真正的语言扩展。
2.1 #lang
指令:通往语言多样性的门户
Racket 语言扩展哲学的最直观体现,便是其 #lang
指令。在 Racket 源文件的顶部,这行指令并非一个简单的注释,而是一个关键的声明,它指示 Racket 运行时使用特定的解析器来解释该文件的其余部分。这种机制使单个 Racket 平台能够支持一个由不同语言组成的家族,每种语言都有其自身的语法和语义。
例如,#lang racket
声明了使用核心 Racket 语言,而 #lang scheme
则使用了 Scheme 方言。其他内置的语言还包括用于静态类型编程的 Typed Racket
、用于创建文档的 Scribble
以及用于函数式反应式编程的 FrTime
。这一设计与 Scheme 形成了鲜明的对比,后者的程序通常不使用 #lang
前缀,因此通常无法在 Racket 中直接运行,反之亦然。#lang
机制不仅仅是为教学而生,它还被用于实际应用,例如 Racket 的官方文档就是用 Scribble 编写的。
2.2 Racket 宏系统的解剖
Racket 平台的语言扩展能力,很大程度上归功于其强大的宏系统。与传统 Lisp 宏直接操作原始 S-表达式不同,Racket 的宏以“语法对象”为基础进行操作。
语法对象是一种结构化的数据类型,它不仅包含代码本身,还携带了关键的元数据,如源代码位置、词法上下文和变量绑定信息。正是这些元数据使得 Racket 的宏具有“卫生性”(hygienic)。当宏生成新的代码(例如临时变量 tmp
)时,Racket 的宏展开器会自动对其进行重命名(例如 tmp_1
),以确保它不会与用户代码中已存在的同名变量发生意外冲突。这种自动的卫生机制是 Racket 宏系统的核心优势,它极大地增强了宏的模块化和可靠性,使其比许多其他宏系统更不容易出现“脆弱”或非模块化的行为。
宏展开器是一个递归系统,它在程序被编译或求值之前,遍历语法树并用宏生成的新代码替换宏调用。这个过程将宏系统转变为一个“编译器扩展的应用程序编程接口”(API),允许程序员添加新的语言特性,甚至是全新的语言方言,这些特性在使用上与内置构造无异。这种能力使得 Racket 成为一个名副其实的语言设计平台。
2.3 超越 S-表达式:使用自定义解析器构建语言
Racket 的语言创建能力并非仅限于 S-表达式的语法。借助其强大的“解析器工具库”(parser tools library),开发者可以为新语言实现任意的解析器,从而支持非 Lisp 风格的语法。一个经典的例子是 Racket 的逻辑编程方言,它采用类似于 Datalog 或 Prolog 的语法。
这些语言方言是 Racket 核心理念的直接产物。它们并非简单的库,而是拥有自己语法和语义的独立语言,但它们都构建在同一套 Racket 运行时、编译器和工具之上。
表1: Racket 核心语言方言及其用途
语言方言 | #lang 指令 |
目的与用途 |
---|---|---|
Typed Racket | #lang typed/racket |
提供静态类型系统,允许在无类型代码上进行渐进式类型化,用于类型系统研究与更健壮的应用开发。 |
Scribble | #lang scribble/base |
是一种用于编写文档和生成 HTML 或 PDF 的语言,Racket 官方文档即以此编写。 |
Lazy Racket | #lang lazy |
支持惰性求值(lazy evaluation),用于探索函数式编程的另一种模式。 |
FrTime | #lang frtime |
用于函数式反应式编程(Functional Reactive Programming)。 |
教育语言 | #lang htdp/bsl , etc. |
简化版本的语言,用于循序渐进地教授编程概念,常用于《如何设计程序》课程。 |
第3章:多范式编程的实践
Racket 的多范式特性使其能够在一个统一的环境中,整合并支持多种编程风格。这既是对其 Lisp 遗产的继承,也是对其作为通用语言平台的独特承诺。
3.1 函数式核心:不变性、闭包和高阶抽象
尽管 Racket 摆脱了 Scheme 的极简主义,但其核心仍然根植于强大的函数式编程原则。它鼓励编写简洁、可复用的代码并避免副作用。语言内置支持词法闭包、尾调用以及带有限制功能的延续(delimited continuations)。此外,Racket 还支持不可变的配对和列表,这对于函数式编程至关重要。这些功能使得 Racket 能够优雅地处理复杂的高阶抽象。
3.2 强大的类型系统:从动态到 Typed Racket
Racket 的主语言采用动态强类型系统。这意味着它在运行时捕获类型错误,但能防止因内存操作不当而导致的错误。在此基础上,Racket 的平台哲学得到了充分体现:它提供了一种独立的语言方言
Typed Racket
,它是一种可与无类型 Racket 代码混合使用的渐进式静态类型语言。这种设计使开发者能够根据需要选择合适的类型化程度,而无需强制采用一个单一、笨重的类型系统。
Typed Racket
并非简单地将类型注解强加于 Racket 语言之上,而是通过宏系统来实现,这使得它能够探索新的编程语言设计和类型系统创新。这种在单一工具链中无缝集成不同类型系统的能力,正是 Racket 作为语言设计平台的价值所在。
3.3 其他范式的整合:命令式、面向对象和逻辑编程
为了满足通用编程的需求,Racket 毫不犹豫地整合了其他范式。它支持命令式编程,允许通过 set!
这样的形式对变量进行赋值。它内置了一个基于 mixin 的类系统,用于面向对象编程。例如,GUI 库中的 new frame%
就是一个使用其面向对象特性的具体例子。
Racket 还通过其宏和模块系统,允许开发者创建和使用逻辑编程语言方言。这展示了 Racket 如何将一个单一的基础语言,扩展到能支持多个截然不同的编程范式,而所有这些都由其核心平台所支持。
3.4 模块和契约系统,用于健壮的软件设计
Racket 鼓励开发者使用其模块系统来组织大型程序。该系统旨在提供一种清晰、可控的方式来管理代码库,与传统的 Scheme 实现相比,其模块系统更易于使用。
此外,Racket 还是第一个为高阶编程语言引入“契约系统”(contract system)的语言。这一系统受到 Eiffel 语言的“设计契约”(Design by Contract)理念启发,并将其扩展至适用于高阶值,如一等函数、对象和引用单元格等。契约系统允许开发者在不改变宏和模块系统核心机制的情况下,在模块级别控制语法。这些机制共同表明,尽管 Racket 倡导动态性和灵活性,但它也同样重视通过形式化、可验证的手段来确保软件的正确性和模块化。
第4章:对 Racket 优势与劣势的比较分析
要全面理解 Racket,必须将其置于更广阔的编程语言背景下进行比较。本章将对 Racket 与其 Lisp 家族近亲以及一个主流语言进行深入比较,以揭示其独特的定位和权衡。
4.1 Racket 对比 Scheme:哲学和实用主义的分歧
Racket 和 Scheme 之间的主要差异不仅在于技术实现,更在于其底层哲学。Scheme 是一个由标准(如 R7RS)定义的“小而精”的语言,强调核心的纯粹性。相比之下,Racket 则是一个“大而全”的语言,旨在通过丰富的工具和库来支持实际应用。
Racket 的 #lang
指令是其与 Scheme 的关键分界线。它允许 Racket 成为一个多语言平台,但这也意味着 Racket 程序默认不符合 Scheme 标准,反之亦然。因此,选择 Racket 或 Scheme,实际上是在两种截然不同的理念之间做出选择:前者提供了功能完备、开箱即用的环境和强大的扩展性,但牺牲了与标准实现的兼容性;后者则提供了一个纯粹、极简的画布,更适合于学术研究或追求语言核心简洁性的开发者。
4.2 Racket 对比 Common Lisp:深入探讨动态性与生态系统成熟度
Racket 和 Common Lisp (CL) 都是 Lisp 家族的强大成员,但它们在设计上有着显著的权衡。
- Racket 的优势:Racket 的宏系统因其默认的“卫生性”而备受赞誉,这使得宏开发更安全、更模块化,可以有效避免名称冲突 。Racket 的模块系统也被认为比 CL 的
eval-when
机制更清晰、更容易理解。此外,Racket 自带一个可跨平台工作的图形用户界面(GUI)库。 - Common Lisp 的优势:CL 以其高度的动态性而著称,这使得它在交互式开发方面具有优势。开发者可以在程序运行时修改代码、重新加载模块,甚至从错误堆栈中恢复,这大大加快了调试速度。CL 的面向对象系统(CLOS)和条件系统(condition system)也被认为是业界最强大的功能之一。
- 生态系统与哲学:关于生态系统,存在两种对立的观点。一些用户赞扬 Racket 包管理器的便利性和其核心库的质量。而另一些用户则批评 Racket 的库生态“不成熟”,存在许多由单个开发者维护、文档不足的“半成品”库,称其“负面巴士系数”(negative bus factor)高。这种差异反映了 Racket 在主流应用中的小众地位。这两种语言的核心哲学也不同:Racket 倾向于一种“集权式”的模块系统,强调编译时检查和不变性,以换取更高的健壮性和可预测性;而 CL 则拥抱一种高度动态的、可在运行时任意修改的开放式环境。
4.3 Racket 对比 Python:面向教育和通用脚本的实用比较
Racket 和 Python 都被用作教育和通用脚本语言,但它们之间的实用性差异巨大。
- 性能:Racket 在数值运算方面通常比纯 Python 快得多,有时可达 5 倍以上 20。然而,在处理字符串密集型任务时,两者性能不相上下,这可能是因为 Python 的内置字符串操作是在 C 代码中实现的。
- 实用性与生态系统:Python 拥有一个庞大且成熟的社区,提供了海量的库、工具和在线资源,例如在线自动评分系统和基于 Web 的编辑器。这些实用性优势是 Racket 社区目前无法比拟的。
- 教育与学生接受度:尽管 Racket 在某些学术机构被用于教授计算机科学,但一项调查显示,只有 22% 的学生喜欢 Racket,而仅有 7% 的学生表示未来会继续使用它。这与学生对 Java、C/C++ 和 Python 等主流语言的高接受度形成鲜明对比。Racket 独特的语法和小众地位,使得学生很难将其经验应用到未来的课程或工作中,这构成了其在教育领域的一个显著挑战。
表2: Racket 对比 Common Lisp 对比 Python 的特征
特征 | Racket | Common Lisp (CL) | Python |
---|---|---|---|
核心哲学 | 语言导向编程(LOP),实用主义,多范式 | 高度动态,多范式,“大而全”语言 | 实用,通用,易于学习 |
宏系统 | 卫生宏,基于语法对象,安全可靠 | 非卫生宏,基于 S-表达式,更具挑战性 | 不支持传统宏,使用元类或装饰器 |
开发体验 | 集成 DrRacket IDE,拥有宏步进器 | 极度交互式,可运行时修改代码 | 丰富的命令行工具和第三方编辑器支持 |
模块系统 | 权威式,编译时检查,顶层定义不可变 | 运行时动态加载,可重定义,高度灵活 | 基于文件和导入,易于理解和使用 |
性能 | 数值运算优于纯 Python;字符串处理与 Python 相当 | SBCL 实现性能卓越,通常优于 Racket | 纯 Python 较慢,但 NumPy 等库可实现高性能 |
生态系统 | 核心库质量高,但第三方库较少,存在“负面巴士系数” | 拥有大量“久经沙场”的成熟库 | 生态系统庞大、成熟,资源和库最丰富 |
第5章:Racket 生态系统:工具、库和社区
Racket 的实力不仅体现在语言本身,更在于其功能完备的生态系统,这为开发者提供了高效且一体化的编程体验。
5.1 DrRacket IDE:独特的学习与开发环境
DrRacket 是 Racket 的旗舰集成开发环境(IDE),由 Racket 本身编写而成。它专为初学者和高级用户设计,并提供了许多独特功能。例如,它能够高亮显示错误、提供交互式界面,并具有一个独特的调试功能,可以追溯到变量的定义位置。
最值得注意的是,DrRacket 能够无缝支持 Racket 的自定义语言方言。当新的语言方言使用宏系统时,它会传播足够的源代码信息,使得 DrRacket 能够提供与其内置语言一样强大的调试和导航功能。这使得开发者在处理自定义语言时,能够获得与使用核心 Racket 语言时相似的优质开发体验。
5.2 命令行工具(racket
, raco
):构建和管理项目
除了 IDE,Racket 还提供了强大的命令行工具集。racket
是核心的解释器、编译器和运行时系统。raco
是一个多功能的命令行工具,用于执行各种任务,包括包管理、创建可执行文件和构建文档等。raco pkg
是其包管理工具,功能类似于 Python 的 pip
。它允许开发者轻松安装、卸载和更新第三方软件包,并且任何 Git URL 都可以被视为一个有效的包源。
5.3 关键库与应用领域
Racket 的标准库内容丰富,涵盖了广泛的应用领域。它包括:
- Web 开发:提供
web-server
库,用于构建完整的 Web 应用程序,支持 URL 路由、HTTP 请求处理和模板渲染。 - 图形用户界面(GUI):提供
racket/gui
和2htdp/universe
等库,用于创建交互式界面和游戏。 - 数据分析与科学计算:包含
racket/math
库,用于数值计算;plot
库则可以用于创建图表和可视化。 - 系统编程:Racket 具有事件空间(eventspaces)和管理员(custodians)等原语,能够控制资源管理,使其可以像操作系统一样加载和管理其他程序 1。
此外,Racket 还提供了 ffi/unsafe
库,用于与其他语言(如 C/C++)进行外部函数接口(FFI)交互。一些实验性项目,如RacketScript
,也正在探索将 Racket 编译为 JavaScript (ES6),以实现与 Web 生态的互操作性。
5.4 社区与学习资源:从学术界到在线论坛
Racket 拥有一个活跃的社区,其成员遍布全球。讨论主要集中在官方的 Discourse 论坛、Discord、Reddit 和 IRC 等平台。尽管社区的沟通策略并非统一规划,但它仍然是开发者寻求帮助、分享项目和讨论语言实现细节的重要场所。
在学习资源方面,Racket 拥有大量高质量的文档和书籍。官方文档提供了从入门指南到高级主题的全面教程。对于初学者,推荐阅读《如何设计程序》(How to Design Programs),而对于有经验的程序员,则可以参考《Racket 指南》(The Racket Guide)。还有其他专门针对游戏开发(如 Realm of Racket)、语言设计(如 Beautiful Racket)和形式化语义(如 Semantics Engineering with PLT Redex)的书籍,这反映了 Racket 在不同领域的深度应用。
第6章:实际应用与现实世界用例
Racket 的核心设计理念使其在特定领域发挥着独特的作用,尤其是在学术界和需要高度定制化解决方案的场景中。
6.1 计算机科学教育与教学法
Racket 在计算机科学教育中被广泛采用,这得益于其清晰的语法和其对“如何设计程序”(HtDP)课程的支持。HtDP 课程旨在通过使用简化的教学语言,如 #lang htdp/bsl
,循序渐进地向学生介绍编程概念。DrRacket IDE 的功能,如交互式界面和量身定制的错误报告,进一步简化了教学过程。
一些大学,如美国西北大学和东北大学,都曾使用 Racket 来进行计算机科学入门课程的教学。然而,这种教育方法也面临挑战。调查显示,学生对 Racket 的兴趣和未来使用的意愿较低,这表明 Racket 的小众特性可能会限制其在学生职业生涯中的“迁移价值”。
6.2 学术研究与原型设计
作为一门专为语言设计而生的平台,Racket 自然成为编程语言研究的温床。研究人员可以利用其强大的宏系统和元编程能力,快速原型化新的领域特定语言(DSL)、实验新的语言特性,并研究编译器行为。Racket 的自定义能力使其非常适合需要量身定制解决方案的学术项目。Racket Papers 网站也收录了许多利用 Racket 生态系统进行研究的学术论文。
6.3 商业和小众应用
尽管 Racket 在主流编程领域不常见,但它仍被用于一些商业和小众项目。其应用通常集中在需要利用其语言导向编程或快速原型设计能力的场景。例如,有一本名为《实用排版》(Practical Typography)的书籍,其出版系统 Pollen 就是使用 Racket 创建的。此外,一些商业公司和政府机构,如 Playtomic、Decathlon 和 Cheshire East Council,也被列为 Racket 的用户。这表明,尽管它不是主流选择,但在特定领域,其独特的优势仍然具有商业价值。此外,Racket 也曾被用于视频游戏(如顽皮狗工作室)的脚本语言,以及用于原型化小型 Web 应用。
第7章:Racket 语言的未来发展轨迹
Racket 的发展是一个持续的过程,其核心团队和社区正积极推动其性能、工具和功能的改进。
7.1 近期里程碑与版本更新
Racket 的开发速度稳定,定期发布新版本,每个版本都带来了性能优化、新功能和工具改进。最近的几个主要版本亮点如下:
表3: 近期 Racket 版本亮点
版本 | 发布日期 | 主要新特性与改进 |
---|---|---|
v8.18 | 2025年8月20日 | 支持 Unicode 16.0;DrRacket 支持 Shift-Tab;XML 结构可序列化 |
v8.17 | 2025年5月17日 | 新的 drracket-core 包减少依赖;Typed Racket 支持 treelists |
v8.16 | 2025年3月2日 | treelist 支持扩展;文档搜索功能可用;XML 读取速度提升 |
v8.15 | 2024年11月5日 | 文档搜索结果排序改进;DrRacket 恢复已打开文件 |
v8.12 | 未详 | vector*-extend 等功能被添加 |
7.2 正在进行中的性能与开发计划
Racket 的发展不仅限于功能添加,还包括对其核心运行时系统的持续改进。例如,团队正在进行将 Racket 迁移至 Chez Scheme 运行时系统的工作,以期提升性能 1。此外,
Pycket
和 RacketScript
等实验性项目表明,团队正积极探索 Racket 与其他生态系统(如 Python 和 JavaScript)的互操作性和不同实现方式 1。
在并发性方面,Racket 提供了对“绿色线程”(green threads)和操作系统线程(OS threads)的支持,以及用于资源管理的低级原语,如事件空间(eventspaces)和管理员(custodians),这些都使其成为系统编程的有力工具 1。
7.3 挑战与未来展望
尽管 Racket 拥有强大的技术基础,但其在主流领域的普及仍然面临挑战。其主要障碍在于其小众地位和相对较小的用户群体。这导致了资源、第三方库和外部工具(如自动评分系统)的缺乏,形成了一个“鸡生蛋,蛋生鸡”的困境。
此外,Racket 社区的沟通方式相对分散,且缺乏一个统一的、规划好的推广策略。学生对其学习和未来使用意愿的调查结果也表明,Racket 在教育领域之外的“迁移价值”仍有待验证。因此,尽管 Racket 在技术上具备独特的优势,但其未来的增长将取决于它能否克服这些实用性挑战,并找到在更广泛的编程世界中独特而不可或缺的定位。
结论
Racket 并非仅仅是一种编程语言,而是一个精心设计的平台,旨在为程序员提供前所未有的自由,以创造和探索新的语言。其核心技术——基于语法对象的卫生宏系统——是实现这一理念的基石,它将元编程从一个充满陷阱的领域,转变为一个可供构建稳健、可组合语言特性的可靠工具。
Racket 的优势在于其作为语言设计平台的卓越能力、其功能完备的工具集(如 DrRacket)以及在教育和学术研究领域的深厚根基。它通过其多范式设计,成功地将函数式、命令式和面向对象等多种编程风格融合在一个统一的环境中。
然而,其独特的哲学也带来了明显的权衡。与 Common Lisp 相比,Racket 在运行时动态性和交互式开发方面做出了牺牲,以换取更强的编译时保证和更清晰的模块系统。与 Python 等主流语言相比,Racket 的社区规模和库生态都相对较小,这限制了其在通用软件开发中的应用。
最终,Racket 的价值在于其作为一种赋能工具的角色。对于那些寻求重塑代码与编译器之间关系的程序员,或者希望教授语言设计基本原理的教育工作者来说,Racket 提供了一个无与伦比的环境。但对于那些更看重庞大社区、丰富现成库和传统开发体验的开发者而言,Racket 的小众地位和独特哲学可能构成一个显著的障碍。Racket 的未来将取决于其能否在技术深度和实用广度之间找到平衡,从而在其独特的生态位中继续茁壮成长。